package viper

import (
	"io"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/pingcap/errors"
	"github.com/spf13/viper"

	"github.com/romberli/go-util/constant"
)

const (
	DefaultReloadThreshold = 500 * time.Millisecond
)

type SafeViper struct {
	mutex sync.RWMutex
	viper *viper.Viper

	lastReload      time.Time
	reloadThreshold time.Duration
}

// NewSafeViper returns a new *SafeViper
func NewSafeViper() *SafeViper {
	return &SafeViper{
		viper:           viper.New(),
		lastReload:      time.Now().Add(-constant.Century),
		reloadThreshold: DefaultReloadThreshold,
	}
}

// Reset resets the config file
func (sv *SafeViper) Reset() {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	sv.viper = viper.New()
	sv.lastReload = time.Now().Add(-constant.Century)
	sv.reloadThreshold = DefaultReloadThreshold
}

// SetReloadThreshold sets the reload threshold
func (sv *SafeViper) SetReloadThreshold(threshold time.Duration) {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	sv.reloadThreshold = threshold
}

// SetConfigFile sets the config file
func (sv *SafeViper) SetConfigFile(file string) {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	sv.viper.SetConfigFile(file)
}

// SetConfigType sets the type of the config file
func (sv *SafeViper) SetConfigType(fileType string) {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	sv.viper.SetConfigType(fileType)
}

// ReadConfig sets the config file
func (sv *SafeViper) ReadConfig(in io.Reader) error {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	err := sv.viper.ReadConfig(in)
	if err != nil {
		return errors.Trace(err)
	}

	return nil
}

// SetConfigName sets the name of the config file
func (sv *SafeViper) ReadInConfig() error {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	err := sv.viper.ReadInConfig()
	if err != nil {
		return errors.Trace(err)
	}

	return nil
}

// WatchConfig sets the config file
func (sv *SafeViper) WatchConfig() {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	sv.viper.WatchConfig()
}

// OnConfigChange sets the config file while the config file changes
func (sv *SafeViper) OnConfigChange(handler func(error)) {
	sv.viper.OnConfigChange(func(event fsnotify.Event) {
		now := time.Now()
		if now.Sub(sv.lastReload) < sv.reloadThreshold {
			return
		}

		sv.lastReload = now

		err := sv.ReadInConfig()

		handler(errors.Trace(err))
	})

	sv.WatchConfig()
}

func (sv *SafeViper) WriteConfig() error {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	err := sv.viper.WriteConfig()
	if err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (sv *SafeViper) SafeWriteConfig() error {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	err := sv.viper.SafeWriteConfig()
	if err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (sv *SafeViper) WriteConfigAs(file string) error {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	err := sv.viper.WriteConfigAs(file)
	if err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (sv *SafeViper) SafeWriteConfigAs(file string) error {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	err := sv.viper.SafeWriteConfigAs(file)
	if err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (sv *SafeViper) WriteConfigTo(writer io.Writer) error {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	err := sv.viper.WriteConfigTo(writer)
	if err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (sv *SafeViper) SetDefault(key string, value interface{}) {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	sv.viper.SetDefault(key, value)
}

func (sv *SafeViper) Set(key string, value interface{}) {
	sv.mutex.Lock()
	defer sv.mutex.Unlock()

	sv.viper.Set(key, value)
}

func (sv *SafeViper) AllKeys() []string {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.AllKeys()
}

func (sv *SafeViper) InConfig(key string) bool {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.InConfig(key)
}

func (sv *SafeViper) Get(key string) interface{} {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.Get(key)
}

func (sv *SafeViper) GetBool(key string) bool {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.GetBool(key)
}

func (sv *SafeViper) GetInt(key string) int {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.GetInt(key)
}

func (sv *SafeViper) GetString(key string) string {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.GetString(key)
}

func (sv *SafeViper) GetFloat64(key string) float64 {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.GetFloat64(key)
}

func (sv *SafeViper) GetTime(key string) time.Time {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.GetTime(key)
}

func (sv *SafeViper) GetDuration(key string) time.Duration {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.GetDuration(key)
}

func (sv *SafeViper) GetIntSlice(key string) []int {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.GetIntSlice(key)
}

func (sv *SafeViper) GetStringSlice(key string) []string {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.GetStringSlice(key)
}

func (sv *SafeViper) GetStringMap(key string) map[string]interface{} {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.GetStringMap(key)
}

func (sv *SafeViper) GetStringMapString(key string) map[string]string {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.GetStringMapString(key)
}

func (sv *SafeViper) GetStringMapStringSlice(key string) map[string][]string {
	sv.mutex.RLock()
	defer sv.mutex.RUnlock()

	return sv.viper.GetStringMapStringSlice(key)
}
